#include <iostream>
#include <sstream>

#include <dlfcn.h>

#include "socket.h"

#include "robot.h"

using namespace robot;

class Worker {
	Socket& sock;
	Robot* robot;

	std::stringstream reply;
	size_t reply_size;
	static const size_t reply_quota = 2000;

	Environment env;

	void HandleReply();
	int Poll();

	public:

	Worker(Socket& s, Robot* bot): sock(s), robot(bot), reply_size(0) {}

	void Work();
};

void Worker::HandleReply() {
//	std::cout << reply.str() << std::endl;

	std::string cmd;

	while (reply >> cmd) {
		if (cmd == "width") {
			reply >> env.width;
		} else if (cmd == "height") {
			reply >> env.height;
		} else if (cmd == "x") {
			reply >> env.x;
		} else if (cmd == "y") {
			reply >> env.y;
		} else if (cmd == "vx") {
			reply >> env.vx;
		} else if (cmd == "vy") {
			reply >> env.vy;
		} else if (cmd == "energy") {
			reply >> env.energy;
		} else if (cmd == "energy_max") {
			reply >> env.energy_max;
		} else if (cmd == "health") {
			reply >> env.health;
		} else if (cmd == "fire_damage_max") {
			reply >> env.fire_damage_max;
		} else if (cmd == "turns_before_shot") {
			reply >> env.turns_before_shot;
		} else if (cmd == "terrain") {
			int x, y;
			char c;

			while (reply >> x >> y >> c) {
				env.terrain[Point(x, y)] = BasicSquare(c);
			}
		} else if (cmd == "robots") {
			double x, y;

			while (reply >> x >> y) {
				env.robots.push_back(BasicPoint<double>(x, y));
			}
		} else if (cmd == "treasures") {
			int x, y;
			while (reply >> x >> y) {
				env.treasures.push_back(Point(x, y));
			}
		} else if (cmd == "turn") {
			Action act = robot->Move(env);
			env.terrain.clear();
			env.robots.clear();
			env.treasures.clear();

			std::stringstream ss;

			if (act.stop) {
				ss << " s";
			}
			if (act.ax < 0) {
				ss << " h";
			} else if (act.ax > 0) {
				ss << " l";
			}
			if (act.ay < 0) {
				ss << " k";
			} else if (act.ay > 0) {
				ss << " j";
			}
			if (act.fire) {
				ss << " f " << act.fire_x << " " << act.fire_y << " " << act.fire_damage;
			}
			ss << "\r\n";

			std::string s = ss.str();
//			std::cerr << s;
			sock.Send(s.c_str(), s.size());
		}
	}
}

void Worker::Work() {
	char c;

	while (sock.Receive(&c, sizeof(c))) {
		if (c == '\n') {
			HandleReply();

			reply.str("");
			reply.clear();
			reply_size = 0;

			continue;
		}

		if (++reply_size > reply_quota) {
			std::cerr << "Here" << std::endl;
			throw Exception("Too long reply from server");
		}

		reply << c;
	}
}

const char* usage = "Usage: robot <path/to/shared/object> <nickname> [addr [port]]";

int main(int argc, char* argv[]) {
	if (argc <= 2) {
		std::cerr << usage << std::endl;
		return 0;
	}

	const char* path = argv[1];
	const char* name = argv[2];

	const char* addr = "127.0.0.1";
	unsigned short port = 8888;

	if (argc > 3) {
		addr = argv[3];
	}

	if (argc > 4) {
		std::stringstream ss(argv[4]);
		if (!(ss >> port)) {
			std::cerr << argv[0] << ": bad port value" << std::endl;
			std::cerr << usage << std::endl;
			return 1;
		}
	}

	if (argc > 5) {
		std::cerr << argv[0] << ": too many arguments" << std::endl;
		std::cerr << usage << std::endl;
		return 1;
	}

	try {
		void* dl = dlopen(path, RTLD_LAZY);
		if (dl == 0) {
			throw dlerror();
		}
		void* factory = dlsym(dl, "getRobot");
		if (factory == 0) {
#ifdef __CYGWIN__
			throw "Can't resolve symbol";
#else
			throw dlerror();
#endif
		}

		Robot* robot = ((Robot* (*)())factory)();

		Socket s;
		s.Connect(addr, port);

		{
			std::stringstream ss;
			ss << "name " << name << "\r\n";
			std::string str = ss.str();

			s.Send(str.c_str(), str.size());
		}

		Worker worker(s, robot);
		worker.Work();

		delete robot;

		if (dlclose(dl) == -1) {
			throw dlerror();
		}
	} catch (Exception e) {
		std::cerr << e << std::endl;
		return 1;
	} catch (SocketException e) {
		std::cerr << e << std::endl;
		return 1;
	} catch (const char* e) {
		std::cerr << e << std::endl;
		return 1;
	}

	return 0;
}
